﻿#if NET8_0_OR_GREATER
using System.Diagnostics.CodeAnalysis;
#endif
using System;
using System.Collections.Generic;
using DimensionsHelper.Common.Utils;

namespace DimensionsHelper.Common.Metadata.Axis;

internal
#if NET8_0_OR_GREATER
        ref struct
#else
    class
#endif
    AxisParser
{
    private readonly AxisExpression _axis;
    private readonly string _text;
    private int _pos;

    internal AxisParser(AxisExpression axis, string text)
    {
        _axis = axis;
        _text = text;
    }

    private
#if NET8_0_OR_GREATER
            readonly
#endif
        char CurrentChar()
    {
        return _pos < _text.Length ? _text[_pos] : '\0';
    }

    private
#if NET8_0_OR_GREATER
            readonly
#endif
        bool CurrentCharIs(char c)
    {
        return CurrentChar() == c;
    }


    private
#if NET8_0_OR_GREATER
            readonly
#endif
        bool CurrentCharIs(Func<char, bool> predicate)
    {
        return predicate(CurrentChar());
    }

#if NET8_0_OR_GREATER
        [DoesNotReturn]
#endif
    private static void UnexpectedChar(int pos, char expected)
    {
        throw new Exception($"应为'{expected}'，位置：{pos + 1}");
    }

#if NET8_0_OR_GREATER
        [DoesNotReturn]
#endif
    private static void UnexpectedToken(int pos, string tokenName)
    {
        throw new Exception($"期望外的Token，应为'{tokenName}'，位置：{pos + 1}。");
    }


#if NET8_0_OR_GREATER
        [DoesNotReturn]
#endif
    private static void InvalidAxisElement(int startPos)
    {
        throw new Exception($"无效的轴元素，起始位置：{startPos}。");
    }


    private int ExpectChar(char expected, bool forward)
    {
        int p = _pos;

        if (!CurrentCharIs(expected))
        {
            UnexpectedChar(_pos, expected);
        }

        if (forward)
        {
            NextCharNotEmpty();
        }

        return p;
    }


    private char CurrentCharNotEmpty()
    {
        SkipEmpty();
        return CurrentChar();
    }


    private bool CurrentCharNotEmptyIs(char c)
    {
        return CurrentCharNotEmpty() == c;
    }


    private bool CurrentCharNotEmptyIs(Func<char, bool> predicate)
    {
        return predicate(CurrentCharNotEmpty());
    }


    private char NextChar()
    {
        _pos++;

        if (_pos >= _text.Length)
        {
            return '\0';
        }

        return _text[_pos];
    }


    private char NextCharNotEmpty()
    {
        NextChar();
        SkipEmpty();
        return CurrentChar();
    }


    private
#if NET8_0_OR_GREATER
            readonly
#endif
        char LookAheadChar()
    {
        int p = _pos + 1;

        if (p < _text.Length)
        {
            return _text[p];
        }

        return '\0';
    }


    private string ReadString()
    {
        var charArray = new List<char>();
        // 跳过开始单引号
        ExpectChar('\'', false);
        char c = NextChar();

        while (c != '\0')
        {
            if (c == '\'')
            {
                // 跳过引号
                c = NextChar();
                // '' 视为转义
                if (c != '\'' || c == '\0')
                {
                    SkipEmpty();
                    break;
                }
            }

            // "" 两个双引号视为双引号转义
            if (c == '"' && LookAheadChar() == '"')
            {
                _pos++;
            }

            charArray.Add(c);
            c = NextChar();
        }

        SkipEmpty();
        return new string(charArray.ToArray());
    }


    private string ReadNumber(bool allowFloat)
    {
        int start = _pos;
        var charArray = new List<char>();
        char c = CurrentChar();

        if (!char.IsNumber(c))
        {
            throw new Exception($"应为数字，位置：{start}。");
        }

        charArray.Add(c);
        bool hasDot = false;

        while (c != '\0' && char.IsNumber(c))
        {
            charArray.Add(c);
            c = NextChar();

            if (c == '.')
            {
                if (hasDot)
                {
                    throw new Exception($"无效的数字，位置：{_pos + 1}。");
                }

                charArray.Add('.');
                c = NextChar();
                hasDot = true;
            }
        }

        if (!allowFloat && hasDot)
        {
            throw new Exception($"应为整数，位置：{start + 1}。");
        }

        SkipEmpty();
        return new string(charArray.ToArray());
    }


    private int ReadInteger()
    {
        string number = ReadNumber(false);

        if (!int.TryParse(number, out int res))
        {
            throw new Exception($"'{number}'不是有效的整数。");
        }

        return res;
    }


    private double ReadDouble()
    {
        string number = ReadNumber(true);

        if (!double.TryParse(number, out double res))
        {
            throw new Exception($"'{number}'不是有效的浮点数。");
        }

        return res;
    }


    private string ReadName()
    {
        var charArray = new List<char>();
        char c = CurrentChar();

        if (!StringHelper.IsValidIdentifierStartChar(c))
        {
            throw new Exception($"无效的标识符起始字符'{c}'。");
        }

        while (c != '\0' && StringHelper.IsValidIdentifierPartChar(c))
        {
            charArray.Add(c);
            c = NextChar();
        }

        SkipEmpty();
        return new string(charArray.ToArray());
    }


    private bool ReadBoolean()
    {
        int start = _pos;
        string name = ReadName();

        if (bool.TryParse(name, out bool res))
        {
            return res;
        }

        throw new Exception($"应为布尔值，位置：{start + 1}。");
    }


    private T ReadEnumName<T>() where T : struct
    {
        int start = _pos;
        string name = ReadName();

        if (Enum.TryParse(name, true, out T res))
        {
            return res;
        }

        throw new Exception($"'{name}'不是有效的{typeof(T).Name}值，位置：{start + 1}。");
    }


    private void SkipEmpty()
    {
        char c = CurrentChar();
        while (char.IsWhiteSpace(c) && c != '\0')
        {
            c = NextChar();
        }
    }


    private class AxisElementInfo(string leftId, string rightId, string? description, AxisElementType elementType)
    {
        public string LeftId { get; } = leftId;

        public string RightId { get; } = rightId;

        public string? Description { get; } = description;

        public AxisElementType ElementType { get; } = elementType;
    }


    // [lower][..]           [upper]
    // [name ]['Description'][elementName(]
    private AxisElementInfo StartElement()
    {
        SkipEmpty();

        int start = _pos;

        string leftId = string.Empty;
        string rightId = string.Empty;
        string? description = null;

        AxisElementType type = AxisElementType.Category;

        // left id
        if (CurrentCharNotEmptyIs(StringHelper.IsValidIdentifierStartChar))
        {
            string name = ReadName();
            // 同时忽略名称和描述
            if (CurrentCharNotEmptyIs('('))
            {
                if (!Enum.TryParse(name, true, out AxisElementType elementType))
                {
                    InvalidAxisElement(start);
                }

                return new AxisElementInfo(leftId, rightId, null, elementType);
            }

            leftId = name;
        }

        char c = CurrentCharNotEmpty();
        switch (c)
        {
            // ..
            case '.':
            {
                c = NextChar();
                if (c != '.')
                {
                    UnexpectedChar(_pos + 1, '.');
                }

                // 跳过'.'
                NextChar();
                type = AxisElementType.CategoryRange;
                break;
            }
            // 'Description'
            case '\'':
                description = ReadString();
                break;
        }

        c = CurrentCharNotEmpty();
        // right id or 'element('
        if (StringHelper.IsValidIdentifierStartChar(c))
        {
            string name = ReadName();
            c = CurrentCharNotEmpty();

            if (c == '(')
            {
                if (AxisElement.TryGetElementType(name, out var elementType))
                {
                    type = elementType;
                }
                else
                {
                    InvalidAxisElement(start);
                }
            }
            else
            {
                if (type != AxisElementType.CategoryRange)
                {
                    InvalidAxisElement(start);
                }

                rightId = name;
            }
        }
        // , | } | \0
        else if (c != ',' && c != '[' && c != '}' && c != '\0')
        {
            InvalidAxisElement(start);
        }

        return new AxisElementInfo(leftId, rightId, description, type);
    }


    private void ReadSuffix(AxisElement element)
    {
        char c = NextCharNotEmpty();

        if (!StringHelper.IsValidIdentifierStartChar(c))
        {
            throw new Exception($"无效的轴元素后缀，位置：{_pos + 1}。");
        }

        while (c != ']')
        {
            AxisElementSuffixType suffixType = ReadEnumName<AxisElementSuffixType>();
            ExpectChar('=', true);

            switch (suffixType)
            {
                case AxisElementSuffixType.CalculationScope:
                    element.SetSuffix(suffixType, ReadEnumName<AxisElementCalculationScope>());
                    break;

                case AxisElementSuffixType.Decimals:
                    element.AddParameter(AxisElementParameterType.Number, ReadInteger());
                    break;

                case AxisElementSuffixType.Factor:
                    element.AddParameter(AxisElementParameterType.Number, ReadDouble());
                    break;

                case AxisElementSuffixType.Multiplier:
                case AxisElementSuffixType.Weight:
                    element.SetSuffix(suffixType, ReadString());
                    break;

                // boolean
                case AxisElementSuffixType.CountsOnly:
                case AxisElementSuffixType.IsFixed:
                case AxisElementSuffixType.IsHidden:
                case AxisElementSuffixType.IsHiddenWhenColumn:
                case AxisElementSuffixType.IsHiddenWhenRow:
                case AxisElementSuffixType.IncludeInBase:
                case AxisElementSuffixType.IsUnweighted:
                    element.SetSuffix(suffixType, ReadBoolean());
                    break;
            }

            c = CurrentCharNotEmpty();
            if (c == ',')
            {
                int comma = _pos;
                c = NextCharNotEmpty();

                if (c == ']')
                {
                    UnexpectedChar(comma + 1, ']');
                }
            }
        }

        ExpectChar(']', true);
    }


    private AxisElement ReadElement()
    {
        bool isExclude = false;
        if (CurrentCharNotEmptyIs('^'))
        {
            isExclude = true;
            NextCharNotEmpty();
        }

        AxisElementInfo info = StartElement();
        var element = new AxisElement(info.ElementType) 
        { 
            IsExclude = isExclude 
        };

        if (element.Type != AxisElementType.CategoryRange)
        {
            element.Name = _axis.GetElementName(element.Type, info.LeftId);
            element.Description = info.Description;
        }

        switch (info.ElementType)
        {
            case AxisElementType.CategoryRange:
                element.LowerBound = info.LeftId;
                element.UpperBound = info.RightId;
                break;

            case AxisElementType.Text:
            case AxisElementType.Ntd:
            case AxisElementType.Ppt:
            case AxisElementType.Total:
            case AxisElementType.SubTotal:
            case AxisElementType.EffectiveBase:
                ReadEmptyParameter();
                break;

            case AxisElementType.Base:
            case AxisElementType.UnweightedBase:
                ReadOptionalStringParameter(element);
                break;

            case AxisElementType.Expression:
            case AxisElementType.Derived:
                ReadStringParameter(element);
                break;

            case AxisElementType.Mean:
            case AxisElementType.StdErr:
            case AxisElementType.StdDev:
            case AxisElementType.SampVar:
                ReadMeanLikeParameters(element, false);
                break;

            case AxisElementType.Numeric:
            case AxisElementType.Min:
            case AxisElementType.Max:
            case AxisElementType.Sum:
            case AxisElementType.Median:
            case AxisElementType.Mode:
                ReadMeanLikeParameters(element, true);
                break;

            case AxisElementType.Percentile:
                ReadPercentileParameter(element);
                break;

            case AxisElementType.Net:
            case AxisElementType.Combine:
                ReadCategoriesParameter(element);
                break;

            case AxisElementType.Category:
            default:
                break;
        }

        if (CurrentCharNotEmptyIs('['))
        {
            ReadSuffix(element);
        }

        return element;
    }


    private void ReadCategories(AxisElement element)
    {
        char c = CurrentCharNotEmpty();

        while (c != '\0' && c != '}')
        {
            AxisElement category = ReadElement();

            element.AddParameter(AxisElementParameterType.Element, category);
            c = CurrentCharNotEmpty();

            // 检查 ','
            if (c != '}')
            {
                int comma = ExpectChar(',', true);
                c = CurrentCharNotEmpty();

                if (c == '}')
                {
                    UnexpectedChar(comma, '}');
                }
            }
        }
    }


    // 必须不包含参数
    private void ReadEmptyParameter()
    {
        ExpectChar('(', true);
        ExpectChar(')', true);
    }


    // 只有一个字符串参数
    private void ReadStringParameter(AxisElement element)
    {
        ExpectChar('(', true);
        ExpectChar('\'', false);
        element.AddParameter(AxisElementParameterType.String, ReadString());
        ExpectChar(')', true);
    }


    private void ReadOptionalStringParameter(AxisElement element)
    {
        ExpectChar('(', true);

        if (CurrentCharNotEmpty() == '\'')
        {
            element.AddParameter(AxisElementParameterType.String, ReadString());
        }

        ExpectChar(')', true);
    }


    // ElementName([name][, 'expression'])，两个参数都是可选的。
    private void ReadMeanLikeParameters(AxisElement element, bool requireVarName)
    {
        ExpectChar('(', true);

        // 允许空参数
        if (CurrentCharNotEmptyIs(')'))
        {
            NextCharNotEmpty();
            return;
        }

        if (requireVarName)
        {
            element.AddParameter(AxisElementParameterType.Name, ReadName());
        }
        else
        {
            if (CurrentCharIs(StringHelper.IsValidIdentifierStartChar))
            {
                element.AddParameter(AxisElementParameterType.Name, ReadName());
            }
            else
            {
                UnexpectedToken(_pos + 1, "标识符");
            }
        }

        // 表达式参数
        if (CurrentCharNotEmptyIs(','))
        {
            NextCharNotEmpty();
            element.AddParameter(AxisElementParameterType.String, ReadString());
        }
    }


    // (net|combine)({element1[, element2] ...})
    private void ReadCategoriesParameter(AxisElement element)
    {
        // ({
        ExpectChar('(', true);
        ExpectChar('{', true);

        ReadCategories(element);

        // })
        ExpectChar('}', true);
        ExpectChar(')', true);
    }


    // percentile()
    private void ReadPercentileParameter(AxisElement element)
    {
        ExpectChar('(', true);
        element.AddParameter(AxisElementParameterType.Name, ReadName());
        ExpectChar(',', true);
        element.AddParameter(AxisElementParameterType.Number, ReadDouble());

        if (CurrentCharNotEmptyIs(','))
        {
            NextCharNotEmpty();
            element.AddParameter(AxisElementParameterType.String, ReadString());
        }

        ExpectChar(')', true);
    }


    public void ReadAll(AxisExpression axisExpression)
    {
        char c = CurrentCharNotEmpty();

        while (c != '\0' && c != '}')
        {
            axisExpression.Elements.AddLast(ReadElement());

            c = CurrentCharNotEmpty();
            // ,
            if (c == ',')
            {
                int comma = ExpectChar(',', true);
                c = CurrentCharNotEmpty();
                if (c == '}')
                {
                    UnexpectedChar(comma, '}');
                }
            }
        }
    }
}